/**
 * QUANSHI.com Inc.
 * Copyright (c) 2016-2017 All Rights Reserved.
 */
package com.quanshi.scheduler.job.biz.quartz;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;

import com.quanshi.scheduler.job.biz.model.vo.TriggerInfoVO;
import com.quanshi.scheduler.job.biz.quartz.bean.RemoteHttpJobBean;
import com.quanshi.scheduler.job.biz.quartz.monitor.JobFailMonitor;
import com.quanshi.scheduler.job.biz.quartz.monitor.JobRegistryMonitor;
import com.quanshi.scheduler.job.biz.service.TriggerGroupService;
import com.quanshi.scheduler.job.biz.service.TriggerInfoService;
import com.quanshi.scheduler.job.biz.service.TriggerLogService;
import com.quanshi.scheduler.job.biz.service.TriggerRegistryService;

/**
 * 
 * @author yanxiang.huang 2017-07-10 16:27:34
 */
public final class JobDynamicScheduler implements InitializingBean, ApplicationContextAware
{

    private static final Logger logger = LoggerFactory.getLogger( JobDynamicScheduler.class );

    private static Scheduler scheduler;

    public static TriggerGroupService triggerGroupService;

    public static TriggerInfoService triggerInfoService;

    public static TriggerRegistryService triggerRegistryService;

    public static TriggerLogService triggerLogService;

    @Override
    public void setApplicationContext( ApplicationContext applicationContext ) throws BeansException
    {
        triggerGroupService = applicationContext.getBean( TriggerGroupService.class );
        triggerInfoService = applicationContext.getBean( TriggerInfoService.class );
        triggerRegistryService = applicationContext.getBean( TriggerRegistryService.class );
        triggerLogService = applicationContext.getBean( TriggerLogService.class );
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {
        Assert.notNull( scheduler, "quartz scheduler can't be null." );
        logger.info( "init quartz scheduler success." );
    }
    
    public static boolean checkExists(String jobName, String jobGroup) throws SchedulerException{
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        return scheduler.checkExists(triggerKey);
    }
    
    public static boolean addJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        JobKey jobKey = new JobKey(jobName, jobGroup);
        
        if (checkExists(jobName, jobGroup)) {
            logger.info("add job fail, job already exist. jobGroup:{}, jobName:{}", jobGroup, jobName);
            return false;
        }
        
        // CronTrigger : TriggerKey + cronExpression    
        // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();

        // JobDetail : jobClass
        Class<? extends Job> jobClass_ = RemoteHttpJobBean.class;
        
        JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
        
        // schedule : jobDetail + cronTrigger
        Date date = scheduler.scheduleJob(jobDetail, cronTrigger);

        logger.info("add job success, jobDetail:{}, cronTrigger:{}, date:{}.", jobDetail, cronTrigger, date);
        return true;
    }
    
    public static boolean rescheduleJob(String jobGroup, String jobName, String cronExpression) throws SchedulerException {
        if (!checkExists(jobName, jobGroup)) {
            logger.info("reschedule job fail, job not exists. jobGroup:{}, jobName:{}.", jobGroup, jobName);
            return false;
        }
        
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        if (oldTrigger != null) {
            // avoid repeat
            String oldCron = oldTrigger.getCronExpression();
            if (oldCron.equals(cronExpression)){
                return true;
            }

            // CronTrigger : TriggerKey + cronExpression
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
            oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();

            // rescheduleJob
            scheduler.rescheduleJob(triggerKey, oldTrigger);
        } else {
            // CronTrigger : TriggerKey + cronExpression
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
            CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();

            // JobDetail
            JobKey jobKey = new JobKey(jobName, jobGroup);
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);

            // Trigger fresh
            HashSet<Trigger> triggerSet = new HashSet<Trigger>();
            triggerSet.add(cronTrigger);

            scheduler.scheduleJob(jobDetail, triggerSet, true);
        }

        logger.info("reschedule job success. jobGroup:{}, jobName:{}.", jobGroup, jobName);
        return true;
    }
    
    public static boolean removeJob(String jobName, String jobGroup) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        boolean result = false;
        if (checkExists(jobName, jobGroup)) {
            result = scheduler.unscheduleJob(triggerKey);
            logger.info("remove job. triggerKey:{}, result:{}.", triggerKey, result);
        }
        return true;
    }
    
    public static boolean pauseJob(String jobName, String jobGroup) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        boolean result = false;
        if (checkExists(jobName, jobGroup)) {
            scheduler.pauseTrigger(triggerKey);
            result = true;
            logger.info("pause job success. triggerKey:{}.", triggerKey);
        } else {
            logger.info("pause job fail. triggerKey:{}.", triggerKey);
        }
        return result;
    }
    
    public static boolean resumeJob(String jobName, String jobGroup) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        boolean result = false;
        if (checkExists(jobName, jobGroup)) {
            scheduler.resumeTrigger(triggerKey);
            result = true;
            logger.info("resume job success. triggerKey:{}.", triggerKey);
        } else {
            logger.info("resume job fail. triggerKey:{}.", triggerKey);
        }
        return result;
    }
    
    public static boolean triggerJob(String jobName, String jobGroup) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName, jobGroup);
        boolean result = false;
        if (checkExists(jobName, jobGroup)) {
            scheduler.triggerJob(jobKey);
            result = true;
            logger.info("run job success, jobKey:{}.", jobKey);
        } else {
            logger.info("run job fail, jobKey:{}.", jobKey);
        }
        return result;
    }

    public static List<Map<String, Object>> getJobList()
    {
        List<Map<String, Object>> jobList = new ArrayList<Map<String, Object>>();

        try
        {
            if ( scheduler.getJobGroupNames() == null || scheduler.getJobGroupNames().size() == 0 )
            {
                return null;
            }
            String groupName = scheduler.getJobGroupNames().get( 0 );
            Set<JobKey> jobKeys = scheduler.getJobKeys( GroupMatcher.jobGroupEquals( groupName ) );
            if ( jobKeys != null && jobKeys.size() > 0 )
            {
                for ( JobKey jobKey : jobKeys )
                {
                    TriggerKey triggerKey = TriggerKey.triggerKey( jobKey.getName(), Scheduler.DEFAULT_GROUP );
                    Trigger trigger = scheduler.getTrigger( triggerKey );
                    JobDetail jobDetail = scheduler.getJobDetail( jobKey );
                    TriggerState triggerState = scheduler.getTriggerState( triggerKey );
                    Map<String, Object> jobMap = new HashMap<String, Object>();
                    jobMap.put( "TriggerKey", triggerKey );
                    jobMap.put( "Trigger", trigger );
                    jobMap.put( "JobDetail", jobDetail );
                    jobMap.put( "TriggerState", triggerState );
                    jobList.add( jobMap );
                }
            }

        }
        catch ( SchedulerException e )
        {
            e.printStackTrace();
            return null;
        }
        return jobList;
    }
    
    public static void fillJobInfo( TriggerInfoVO jobInfo )
    {
        // TriggerKey : name + group
        String group = String.valueOf( jobInfo.getJobGroup() );
        String name = String.valueOf( jobInfo.getId() );
        TriggerKey triggerKey = TriggerKey.triggerKey( name, group );
        try
        {
            TriggerState triggerState = scheduler.getTriggerState( triggerKey );
            if ( triggerState != null )
            {
                jobInfo.setJobStatus( triggerState.name() );
            }
        }
        catch ( SchedulerException e )
        {
            e.printStackTrace();
        }
    }

    public void setScheduler( Scheduler scheduler )
    {
        JobDynamicScheduler.scheduler = scheduler;
    }

    public void init() throws Exception
    {
        JobRegistryMonitor.getInstance().start();
        JobFailMonitor.getInstance().start();
    }

    public void destroy()
    {
        JobRegistryMonitor.getInstance().toStop();
        JobFailMonitor.getInstance().toStop();
    }

}
